package com.bugsnag.ohos.reactnative;


import com.bugsnag.ohos.Breadcrumb;
import com.bugsnag.ohos.Client;
import com.bugsnag.ohos.Error;
import com.bugsnag.ohos.Event;
import com.bugsnag.ohos.NativeInterface;
import com.bugsnag.ohos.Severity;
import com.bugsnag.ohos.SeverityReason;
import com.bugsnag.ohos.Stackframe;
import com.bugsnag.ohos.Thread;
import com.bugsnag.ohos.User;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class EventDeserializer implements MapDeserializer<Event> {
    private AppDeserializer appDeserializer;
    private DeviceDeserializer deviceDeserializer;
    private StackframeDeserializer stackframeDeserializer;
    private ErrorDeserializer errorDeserializer;
    private ThreadDeserializer threadDeserializer;
    private BreadcrumbDeserializer breadcrumbDeserializer;
    private Collection<String> projectPackages;
    private Client client;

    public EventDeserializer(Client client, Collection<String> projectPackages) {
        appDeserializer = new AppDeserializer();
        deviceDeserializer = new DeviceDeserializer();
        stackframeDeserializer = new StackframeDeserializer();
        errorDeserializer = new ErrorDeserializer(stackframeDeserializer, client.getLogger());
        threadDeserializer = new ThreadDeserializer(stackframeDeserializer, client.getLogger());
        breadcrumbDeserializer = new BreadcrumbDeserializer(client.getLogger());
        this.projectPackages = projectPackages;
        this.client = client;
    }

    @Override
    public Event deserialize(Map<String, Object> map) {
        Map<String, Object> severityReason = (Map<String, Object>) map.get("severityReason");

        String severityReasonType = (String) severityReason.get("type");
        String severity = (String) map.get("severity");
        boolean unhandled = (boolean) map.get("unhandled");
        boolean originalUnhandled = getOriginalUnhandled(severityReason, unhandled);
        SeverityReason handledState = new SeverityReason(
            severityReasonType,
            Severity.valueOf(severity.toUpperCase(Locale.US)),
            unhandled,
            originalUnhandled,
            null
        );

        // construct event
        Event event = NativeInterface.createEvent(null, client, handledState);
        event.setContext((String) map.get("context"));
        event.setGroupingHash((String) map.get("groupingHash"));
        // app/device
        event.setApp(appDeserializer.deserialize((Map<String, Object>) map.get("app")));
        event.setDevice(deviceDeserializer.deserialize((Map<String, Object>) map.get("device")));

        // user
        User user = new UserDeserializer().deserialize((Map<String, Object>) map.get("user"));
        event.setUser(user.id, user.email, user.name);

        // errors
        List<Map<String, Object>> errors = (List<Map<String, Object>>) map.get("errors");
        event.getErrors().clear();
        List errorList = new ArrayList<>();
        for (Map<String, Object> mapItem : errors) {
            Error error = errorDeserializer.deserialize(mapItem);
            errorList.add(error);
        }
        event.getErrors().addAll(errorList);

        // if the JS payload has passed down a native stacktrace,
        // construct a second error object from it and append it to the event
        // so both stacktraces are visible to the user
        if (map.containsKey("nativeStack") && event.getErrors().isEmpty()) {
            Error jsError = event.getErrors().get(0);
            NativeStackDeserializer nativeStackDeserializer = new NativeStackDeserializer(projectPackages, client.getConfig());
            List<Stackframe> nativeStack = nativeStackDeserializer.deserialize(map);
            jsError.getStacktrace().addAll(0, nativeStack);
        }

        // threads
        List<Map<String, Object>> threads = (List<Map<String, Object>>) map.get("threads");
        event.getThreads().clear();
        List desArray = new ArrayList<>();
        for (Map<String, Object> mapItem : threads) {
            Thread thread = threadDeserializer.deserialize(mapItem);
            desArray.add(thread);
        }
        event.getThreads().addAll(desArray);

        // breadcrumbs
        List<Map<String, Object>> breadcrumbs = (List<Map<String, Object>>) map.get("breadcrumbs");
        event.getBreadcrumbs().clear();
        List<Breadcrumb> breadcrumbList = new ArrayList<>();
        for (Map<String, Object> crubsItem : breadcrumbs) {
            Breadcrumb breadcrumb = breadcrumbDeserializer.deserialize(crubsItem);
            breadcrumbList.add(breadcrumb);
        }
        event.getBreadcrumbs().addAll(breadcrumbList);

        // metadata
        Map<String, Object> metadata = (Map<String, Object>) map.get("metadata");
        Iterator iterator = metadata.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Object> entry = (Map.Entry<String, Object>) iterator.next();
            event.addMetadata(entry.getKey(), (Map<String, Object>) entry.getValue());
        }
        return event;
    }

    private boolean getOriginalUnhandled(Map<String, Object> map, boolean unhandled) {
        boolean unhandledOverridden = map.get("unhandledOverridden") == null ? false : (boolean) map.get("unhandledOverridden");
        if (unhandledOverridden != unhandled)
            return true;
        else return unhandled;
    }
}
